package org.wsff.tools.api.gateway.impl.spring;

import java.lang.annotation.Annotation;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Service;
import org.wsff.tools.api.gateway.core.Proxy;

import com.google.common.collect.Lists;

/**
 * ProxySpringBeanScanner
 * @author ryan
 * @version Id: ProxySpringBeanScanner.java, v 0.1 2022-02-08 17:24 ryan Exp $$
 */
@Service
public class ProxySpringBeanScanner implements BeanDefinitionRegistryPostProcessor {

    /**
     * Modify the application context's internal bean definition registry after its
     * standard initialization. All regular bean definitions will have been loaded,
     * but no beans will have been instantiated yet. This allows for adding further
     * bean definitions before the next post-processing phase kicks in.
     * @param registry the bean definition registry used by the application context
     * @throws BeansException in case of errors
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
        scanner.setBeanNameGenerator(new AnnotationBeanNameGenerator());
        List<Class<? extends Annotation>> types = getScanAnnotationTypes();
        for (Class<? extends Annotation> type : types) {
            scanner.addIncludeFilter(new AnnotationTypeFilter(type));
        }
        scanner.scan(getScanBasePackages());
    }

    /**
     * 获取注解扫描路径
     *
     * @return 扫描路径
     */
    protected String[] getScanBasePackages() {
        return new String[] { "" };
    }

    /**
     * 获取需要扫描的自定义注解
     *
     * @return 注解
     */
    protected List<Class<? extends Annotation>> getScanAnnotationTypes() {
        return Lists.newArrayList(Proxy.class);
    }

    /**
     * Modify the application context's internal bean factory after its standard
     * initialization. All bean definitions will have been loaded, but no beans
     * will have been instantiated yet. This allows for overriding or adding
     * properties even to eager-initializing beans.
     * @param beanFactory the bean factory used by the application context
     * @throws BeansException in case of errors
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        List<Class<? extends Annotation>> annotationTypes = getScanAnnotationTypes();
        for (Class<? extends Annotation> type : annotationTypes) {
            String[] names = beanFactory.getBeanNamesForAnnotation(type);
            if (names == null || names.length <= 0) {
                continue;
            }
            for (String beanName : names) {
                Class<?> cls = beanFactory.getType(beanName);
                if (cls.getAnnotations() == null || cls.getAnnotations().length <= 0) {
                    continue;
                }
                for (Annotation annotation : cls.getAnnotations()) {
                    // 注册全限定名
                    this.registerAlias(beanFactory, beanName, cls.getName());

                    // 注册别名
                    if (type.isAssignableFrom(Proxy.class)) {
                        Proxy proxy = (Proxy) annotation;
                        if (StringUtils.isNotBlank(proxy.value())) {
                            this.registerAlias(beanFactory, beanName, proxy.value());
                        }
                    }
                }
            }
        }
    }

    /**
     * 为bean注册别名
     * 注意：如果别名与bean的ID冲突，放弃别名注册
     *
     * @param factory ConfigurableListableBeanFactory
     * @param beanId bean的ID
     * @param value Interface的value
     */
    private void registerAlias(ConfigurableListableBeanFactory factory, String beanId, String value) {
        //防止别名覆盖bean的ID
        if (!factory.containsBeanDefinition(value)) {
            factory.registerAlias(beanId, value);
        }
    }

}
